/*
 * Copyright 2016 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.apiman.tools.ldap;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Hashtable;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.directory.server.annotations.CreateLdapServer;
import org.apache.directory.server.annotations.CreateTransport;
import org.apache.directory.server.core.integ.AbstractLdapTestUnit;
import org.apache.directory.server.core.integ.FrameworkRunner;
import org.apache.directory.server.core.partition.impl.btree.jdbm.JdbmPartition;
import org.apache.directory.server.i18n.I18n;
import org.apache.directory.shared.ldap.entry.DefaultServerEntry;
import org.apache.directory.shared.ldap.entry.ServerEntry;
import org.apache.directory.shared.ldap.ldif.LdifEntry;
import org.apache.directory.shared.ldap.ldif.LdifReader;
import org.apache.directory.shared.ldap.name.DN;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * A simple ldap server.
 *
 * @author eric.wittmann@redhat.com
 */
@SuppressWarnings("nls")
@RunWith(FrameworkRunner.class)
@CreateLdapServer(transports = { @CreateTransport(protocol = "LDAP", port = 7654) })
public class ApimanLdapServer extends AbstractLdapTestUnit {

    private static final String LDAP_SERVER = "localhost";

    private static JdbmPartition partition;

    @Before
    public void setUp() throws Exception {
        if (partition != null) {
            return;
        }
        File targetDir = new File("target");
        if (!targetDir.isDirectory()) {
            throw new Exception("Couldn't find maven target directory: " + targetDir);
        }
        File partitionDir = new File(targetDir, "_ldap-partition");
        if (partitionDir.exists()) {
            FileUtils.deleteDirectory(partitionDir);
        }
        partitionDir.mkdirs();

        final File partitionDirectory = partitionDir;
        partition = new JdbmPartition();
        partition.setId("apiman");
        partition.setPartitionDir(partitionDirectory);
        partition.setSchemaManager(service.getSchemaManager());
        partition.setSuffix("o=apiman");
        service.addPartition(partition);

        // Inject the foo root entry if it does not already exist
        try {
            service.getAdminSession().lookup(partition.getSuffixDn());
        } catch (Exception lnnfe) {
            DN dn = new DN("o=apiman");
            ServerEntry entry = service.newEntry(dn);
            entry.add("objectClass", "top", "domain", "extensibleObject");
            entry.add("dc", "apiman");
            entry.add("cn", "o=apiman");
            service.getAdminSession().add(entry);
        }

        try {
            injectLdifFiles("io/apiman/tools/ldap/users.ldif");
        } catch (Exception e) {
            throw e;
        }
    }

    @Test
    public void startLdapServer() throws Exception {
        DirContext ctx = createContext();
        Assert.assertNotNull(ctx);

        SearchControls controls = new SearchControls();
        controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
        NamingEnumeration<SearchResult> result = ctx.search("o=apiman", "(ObjectClass=*)", controls);

        int count = 0;
        while (result.hasMore()) {
            result.next();
            count++;
        }

        String url = "ldap://" + LDAP_SERVER + ":" + ldapServer.getPort();
        System.out.println("======================================================");
        System.out.println("LDAP server started successfully.");
        System.out.println("");
        System.out.println("  URL: " + url);
        System.out.println("  Node Count: " + count);
        System.out.println("  Direct Bind DN: cn=${username},ou=developers,ou=people,o=apiman");
        System.out.println("======================================================");
        System.out.println("");
        System.out.println("");
        System.out.println("Press Enter to stop the LDAP server.");
        new BufferedReader(new InputStreamReader(System.in)).readLine();
        System.out.println("Shutting down the LDAP server...");
    }

    public static void injectLdifFiles(String... ldifFiles) throws Exception {
        if (ldifFiles != null && ldifFiles.length > 0) {
            for (String ldifFile : ldifFiles) {
                InputStream is = null;
                try {
                    is = ApimanLdapServer.class.getClassLoader().getResourceAsStream(ldifFile);
                    if (is == null) {
                        throw new FileNotFoundException("LDIF file '" + ldifFile + "' not found.");
                    } else {
                        try {
                            LdifReader ldifReader = new LdifReader(is);
                            for (LdifEntry entry : ldifReader) {
                                injectEntry(entry);
                            }
                            ldifReader.close();
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                } finally {
                    IOUtils.closeQuietly(is);
                }
            }
        }
    }

    private static void injectEntry(LdifEntry entry) throws Exception {
        if (entry.isChangeAdd()) {
            service.getAdminSession().add(
                    new DefaultServerEntry(service.getSchemaManager(), entry.getEntry()));
        } else if (entry.isChangeModify()) {
            service.getAdminSession().modify(entry.getDn(), entry.getModificationItems());
        } else {
            String message = I18n.err(I18n.ERR_117, entry.getChangeType());
            throw new NamingException(message);
        }
    }

    private DirContext createContext() throws NamingException {
        // Create a environment container
        Hashtable<Object, Object> env = new Hashtable<>();

        String url = "ldap://" + LDAP_SERVER + ":" + ldapServer.getPort();

        // Create a new context pointing to the partition
        env.put(Context.PROVIDER_URL, url);
        env.put(Context.SECURITY_PRINCIPAL, "uid=admin,ou=system");
        env.put(Context.SECURITY_CREDENTIALS, "secret");
        env.put(Context.SECURITY_AUTHENTICATION, "simple");
        env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");

        // Let's open a connection on this partition
        InitialContext initialContext = new InitialContext(env);

        // We should be able to read it
        DirContext appRoot = (DirContext) initialContext.lookup("");
        Assert.assertNotNull(appRoot);

        return appRoot;
    }

}
